package frame.options.simulation;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map.Entry;

import model.algorithms.embedding.EmbeddingAlgorithm;
import model.algorithms.linksMapping.LinksMappingAlgorithm;
import model.algorithms.nodesMapping.Greedy;
import model.algorithms.nodesMapping.NodesMappingAlgorithm;
import model.algorithms.rounding.RoundingAlgorithm;
import model.algorithms.splitting.SplittingAlgorithm;
import model.components.links.link.VirtualLink;
import model.components.links.link.PhysicalLink;
import model.components.networks.network.VirtualNetwork;
import model.components.networks.network.PhysicalNetwork;
import model.components.networks.network.Network;
import model.components.nodes.node.VirtualNode;
import model.components.nodes.node.PhysicalNode;
import model.components.nodes.node.VirtualServer;

public class Simulation
{
	public static final String CANCELED_STATE = "Canceled";
	public static final String DONE_STATE     = "Done";
	public static final String READY_STATE    = "Ready";
	
	private String name;
	public String getName(){ return name; }
	public void setName( String name ){ this.name = name; }
	
	private String state;
	public String getState(){ return state; }
	public void setState( String state ){ this.state = state; }
	
	private List< PhysicalNetwork > physicalNetworks;
	public List< PhysicalNetwork > getPhysicalNetworks(){ return physicalNetworks; }	
	public void setPhysicalNetworks( List< PhysicalNetwork > physicalNetworks ){ this.physicalNetworks = physicalNetworks; }
	
	private List< VirtualNetwork > virtualNetworks;
	public List< VirtualNetwork > getVirtualNetworks(){ return virtualNetworks; }
	public void setVirtualNetworks( List< VirtualNetwork > virtualNetworks ){ this.virtualNetworks = virtualNetworks; }
	
	private SplittingAlgorithm splittingAlgorithm;
	public SplittingAlgorithm getSplittingAlgorithm(){ return splittingAlgorithm; }
	public void setSplittingAlgorithm( SplittingAlgorithm splittingAlgorithm ){ this.splittingAlgorithm = splittingAlgorithm; }
	
	private NodesMappingAlgorithm nodesMappingAlgorithm;
	public NodesMappingAlgorithm getNodesMappingAlgorithm(){ return nodesMappingAlgorithm; }
	public void setNodesMappingAlgorithm( NodesMappingAlgorithm nodesMappingAlgorithm ){ this.nodesMappingAlgorithm = nodesMappingAlgorithm; }
	
	private EmbeddingAlgorithm embeddingAlgorithm;
	public EmbeddingAlgorithm getEmbeddingAlgorithm(){ return embeddingAlgorithm; }
	public void setEmbeddingAlgorithm( EmbeddingAlgorithm embeddingAlgorithm ){ this.embeddingAlgorithm = embeddingAlgorithm; }
	
	private RoundingAlgorithm roundingAlgorithm;
	public RoundingAlgorithm getRoundingAlgorithm(){ return roundingAlgorithm; }
	public void setRoundingAlgorithm( RoundingAlgorithm roundingAlgorithm ){ this.roundingAlgorithm = roundingAlgorithm; }
	
	private LinksMappingAlgorithm linksMappingAlgorithm;
	public LinksMappingAlgorithm getLinksMappingAlgorithm(){ return linksMappingAlgorithm; }
	public void setLinksMappingAlgorithm( LinksMappingAlgorithm linksMappingAlgorithm ){ this.linksMappingAlgorithm = linksMappingAlgorithm; }
	
	private int rejectedVirtualNetworksNumber;
	public int getRejectedVirtualNetworksNumber(){ return rejectedVirtualNetworksNumber; }
	public void setRejectedVirtualNetworksNumber( int rejectedVirtualNetworksNumber ){ this.rejectedVirtualNetworksNumber = rejectedVirtualNetworksNumber; }
	
	public Simulation( List< PhysicalNetwork > physicalNetworks, List< VirtualNetwork > virtualNetworks, SplittingAlgorithm splittingAlgorithm, NodesMappingAlgorithm nodesMappingAlgorithm, EmbeddingAlgorithm embeddingAlgorithm, RoundingAlgorithm roundingAlgorithm, LinksMappingAlgorithm linksMappingAlgorithm )
	{
		this.setPhysicalNetworks( physicalNetworks );
		this.setVirtualNetworks( virtualNetworks );
		this.setSplittingAlgorithm( splittingAlgorithm );
		this.setNodesMappingAlgorithm( nodesMappingAlgorithm );
		this.setEmbeddingAlgorithm( embeddingAlgorithm );
		this.setRoundingAlgorithm( roundingAlgorithm );
		this.setLinksMappingAlgorithm( linksMappingAlgorithm );
		
		if( this.getPhysicalNetworks().size() > 1 )
			splittingAlgorithm.setPhysicalNetworks( physicalNetworks );
		
		if( embeddingAlgorithm == null )
			this.setName( splittingAlgorithm.getName() + "_" + nodesMappingAlgorithm.getName() + "_" + linksMappingAlgorithm.getName() );
		else
			this.setName( splittingAlgorithm.getName() + "_" + embeddingAlgorithm.getName() + "_" + roundingAlgorithm.getName() + "_" + linksMappingAlgorithm.getName() );
		
		this.setState( READY_STATE );
	}
	
	public int run( int timeInstant )
	{
		this.physicalNetworksReleasing( this.getCompletedVirtualNetworks( timeInstant ) );
		List< VirtualNetwork > startingVirtualNetworks = this.getStartingVirtualNetworks( timeInstant );
		
		for( VirtualNetwork startingVirtualNetwork: startingVirtualNetworks )
		{
			LinkedHashMap< String, LinkedHashMap< VirtualNode, PhysicalNode > > virtualNodesMappings = new LinkedHashMap< String, LinkedHashMap< VirtualNode, PhysicalNode > >();
			LinkedHashMap< String, int[][][][] > virtualLinksMappings = new LinkedHashMap< String, int[][][][] >();
			
			List< VirtualNetwork > virtualSubnetworks = new ArrayList< VirtualNetwork >();
			
			if( this.getPhysicalNetworks().size() == 1 )
			{	
				int i = 0;
				
				for( VirtualNode virtualNode: startingVirtualNetwork.getVirtualNodes() )
					virtualNode.setNumber( i++ );
				
				i = 0;
				
				for( VirtualLink virtualLink: startingVirtualNetwork.getVirtualLinks() )
					virtualLink.setNumber( i++ );
				
				startingVirtualNetwork.setPhysicalNetworkName( this.getPhysicalNetworks().get( 0 ).getName() );
				
				virtualSubnetworks.add( startingVirtualNetwork );
			}
			else
				virtualSubnetworks = this.getSplittingAlgorithm().run( startingVirtualNetwork );
			
			startingVirtualNetwork.setVirtualSubnetworks( virtualSubnetworks );
			
			if( virtualSubnetworks == null )
			{
				System.out.println( "SplittingAlgorithm KO" );
				startingVirtualNetwork.setDenied( true );
			}
			else
			{
				for( PhysicalNetwork physicalNetwork: this.getPhysicalNetworks() )
				{
					if( startingVirtualNetwork.isDenied() )
						break;
					
					for( VirtualNetwork virtualSubnetwork: virtualSubnetworks )
					{
						if( virtualSubnetwork.getPhysicalNetworkName().equals( physicalNetwork.getName() ) )
							if( virtualSubnetwork.getLinks().size() == 0 || virtualSubnetwork.getNodes().size() == 1 )
							{
								LinkedHashMap< VirtualNode, PhysicalNode > virtualNodesMapping = ( new Greedy( "Greedy" ) ).run( virtualSubnetwork, physicalNetwork );
					
								if( virtualNodesMapping != null )
								{
									virtualNodesMappings.put( physicalNetwork.getName(), virtualNodesMapping );
								}
								else if( virtualNodesMapping == null )
								{
									System.out.println( "Greedy KO" );
									startingVirtualNetwork.setDenied( true );
									
									break;
								}
							}
							else if( virtualSubnetwork.getVirtualLinks().size() > 0 )
							{
								LinkedHashMap< VirtualNode, PhysicalNode > virtualNodesMapping = null;
								int virtualLinksMapping[][][][] = null;
															
								if( this.getEmbeddingAlgorithm() == null )
								{
									virtualNodesMapping = this.getNodesMappingAlgorithm().run( virtualSubnetwork, physicalNetwork );
									
									if( virtualNodesMapping == null )
									{
										System.out.println( "NodesMappingAlgorithm KO" );
										startingVirtualNetwork.setDenied( true );
										
										break;
									}
								}
								else if( this.getEmbeddingAlgorithm() != null )
								{
									if( this.getEmbeddingAlgorithm().run( virtualSubnetwork, physicalNetwork ) )
									{
										virtualNodesMapping = this.getRoundingAlgorithm().run( virtualSubnetwork, physicalNetwork, this.getEmbeddingAlgorithm().getXValues(), this.getEmbeddingAlgorithm().getFValues() );
	
										if( virtualNodesMapping == null )
										{
											System.out.println( "RoundingAlgorithm KO" );
											startingVirtualNetwork.setDenied( true );
										}
									}
									else
									{
										System.out.println( "EmbeddingAlgorithm KO" );
										startingVirtualNetwork.setDenied( true );
									}
								}
								
								virtualLinksMapping = this.getLinksMappingAlgorithm().run( virtualSubnetwork, physicalNetwork, virtualNodesMapping );
								
								if( virtualLinksMapping == null )
								{
									System.out.println( "LinksMappingAlgorithm KO" );
									startingVirtualNetwork.setDenied( true );

									break;
								}
								
								virtualNodesMappings.put( physicalNetwork.getName(), virtualNodesMapping );
								virtualLinksMappings.put( physicalNetwork.getName(), virtualLinksMapping );
							}
					}
				}
			}
			
			if( !startingVirtualNetwork.isDenied() )
			{
				startingVirtualNetwork.setState( Network.ACCEPTED_STATE );
				startingVirtualNetwork.setChange( true );
				
				for( PhysicalNetwork physicalNetwork: this.getPhysicalNetworks() )				
					for( VirtualNetwork virtualSubnetwork: virtualSubnetworks )					
						if( virtualSubnetwork.getPhysicalNetworkName().equals( physicalNetwork.getName() ) )
						{
							virtualSubnetwork.setDenied( false );
							
							if( virtualSubnetwork.getNodes().size() > 0 )
							{
								Iterator< Entry< String, LinkedHashMap< VirtualNode, PhysicalNode > > > iterator = virtualNodesMappings.entrySet().iterator();
					       
								while( iterator.hasNext() )
								{
									Entry< String, LinkedHashMap< VirtualNode, PhysicalNode > > virtualNodesMapping = iterator.next();
									
									if( virtualSubnetwork.getPhysicalNetworkName().equals( virtualNodesMapping.getKey() ) )
									{
										virtualSubnetwork.setVirtualNodesMapping( virtualNodesMapping.getValue() );
										this.physicalNodesAllocating( virtualSubnetwork, physicalNetwork );
										break;
									}
								}
							}
							
							if( virtualSubnetwork.getLinks().size() > 0 )
							{
								Iterator< Entry< String, int[][][][] > > virtualLinksMappingIterator = virtualLinksMappings.entrySet().iterator();

								while( virtualLinksMappingIterator.hasNext() )
								{
									Entry< String, int[][][][] > virtualLinksMapping = virtualLinksMappingIterator.next();
									
									if( virtualSubnetwork.getPhysicalNetworkName().equals( virtualLinksMapping.getKey() ) )
									{
										virtualSubnetwork.setVirtualLinksMapping( virtualLinksMapping.getValue() );
										this.physicalLinksAllocating( virtualSubnetwork, physicalNetwork );
										break;
									}
								}
							}
				       
							physicalNetwork.setChange( true );
						}
			}
			else
			{
				startingVirtualNetwork.setState( Network.REJECTED_STATE );
				this.setRejectedVirtualNetworksNumber( this.getRejectedVirtualNetworksNumber() + 1 );
			}
		}
		
		return this.getRejectedVirtualNetworksNumber();
	}
	
	public int getSimulationEndingDate()
	{
		int simulationEndingDate = 0;
		
		for( VirtualNetwork virtualNetwork: this.getVirtualNetworks() )
			if( virtualNetwork.getEndingDate() > simulationEndingDate )
				simulationEndingDate = virtualNetwork.getEndingDate();
		
		return simulationEndingDate;
	}
	
	public List< VirtualNetwork > getStartingVirtualNetworks( int timeInstant )
	{
		List< VirtualNetwork > startingVirtualNetworks = new ArrayList< VirtualNetwork >();
	
		for( VirtualNetwork virtualNetwork: this.getVirtualNetworks() )
			if( virtualNetwork.getStartingDate() == timeInstant )
				startingVirtualNetworks.add( virtualNetwork );
		
		return startingVirtualNetworks;
	}
	private void physicalNodesAllocating( VirtualNetwork virtualNetwork, PhysicalNetwork physicalNetwork )
	{
		Iterator< PhysicalNode > physicalNodesIterator = physicalNetwork.getPhysicalNodes().iterator();
		
		while( physicalNodesIterator.hasNext() )
		{
			PhysicalNode physicalNode = physicalNodesIterator.next();
						
			Iterator< Entry< VirtualNode, PhysicalNode > > virtualNodesMappingIterator = virtualNetwork.getVirtualNodesMapping().entrySet().iterator();
			
			while( virtualNodesMappingIterator.hasNext() )
			{
				Entry< VirtualNode, PhysicalNode > virtualNodesMapping = virtualNodesMappingIterator.next();
				VirtualNode mappedVirtualNode = virtualNodesMapping.getKey();
				PhysicalNode mappedPhysicalNode = virtualNodesMapping.getValue();
				
				if( physicalNode.getNumber() == mappedPhysicalNode.getNumber() ) 
				{
					physicalNode.setAvailableCpu( physicalNode.getAvailableCpu() - mappedVirtualNode.getCpu() );
			 		physicalNode.setAvailableMemory( physicalNode.getAvailableMemory() - mappedVirtualNode.getMemory() );
			 		
					if( mappedVirtualNode.getType().equals( VirtualNode.SERVER_TYPE ) ) 
						physicalNode.setAvailableDiskSpace( physicalNode.getAvailableDiskSpace() - mappedVirtualNode.getDiskSpace() );
				 	
					physicalNode.setAvailableVirtualNodesNumber( physicalNode.getAvailableVirtualNodesNumber() - 1 );
					
					mappedVirtualNode.setNumber( mappedVirtualNode.getInitialNumber() );
					physicalNode.add( mappedVirtualNode );
					mappedVirtualNode.setPhysicalNode( physicalNode );
					
					mappedVirtualNode.setCpuUnitTimeUnitPrice( physicalNetwork.getCpuUnitTimeUnitPrice() );
					mappedVirtualNode.setMemoryUnitTimeUnitPrice( physicalNetwork.getMemoryUnitTimeUnitPrice() );
					
					if( mappedVirtualNode.getType().equals( VirtualNode.SERVER_TYPE ) )
						( (VirtualServer) mappedVirtualNode ).setDiskSpaceUnitTimeUnitPrice( physicalNetwork.getDiskSpaceUnitTimeUnitPrice() );
				 }
			}
		}
	}
	private void physicalLinksAllocating( VirtualNetwork virtualNetwork, PhysicalNetwork physicalNetwork )
	{
		int virtualLinksMapping[][][][] = virtualNetwork.getVirtualLinksMapping();
		
		if( virtualLinksMapping != null )
			for( int k = 0; k < virtualNetwork.getVirtualLinks().size(); k++ )
				for( int i = 0; i < physicalNetwork.getNodes().size(); i++ )
					for( int j = 0; j < physicalNetwork.getNodes().size(); j++ )
						for( int backupFlag = 0; backupFlag < 2; backupFlag++ )
							if( virtualLinksMapping[ k ][ i ][ j ][ backupFlag ] != 0 )
								for( VirtualLink virtualLink: virtualNetwork.getVirtualLinks() )
									if( virtualLink.getNumber() == k )
										for( PhysicalLink physicalLink: physicalNetwork.getPhysicalLinks() )
											if( i == physicalLink.getFirstNode().getNumber() && j == physicalLink.getSecondNode().getNumber() || i == physicalLink.getSecondNode().getNumber() && j == physicalLink.getFirstNode().getNumber() )
											{
												int bandwidth = virtualLinksMapping[ k ][ i ][ j ][ backupFlag ];
												
												physicalLink.put( virtualLink, bandwidth, backupFlag );
												virtualLink.put( physicalLink, bandwidth, backupFlag );
												virtualLink.setBandwidthUnitTimeUnitPrice( physicalNetwork.getBandwidthUnitTimeUnitPrice() );
											}
	}

	public List< VirtualNetwork > getCompletedVirtualNetworks( int timeInstant )
	{
		List< VirtualNetwork > completedVirtualNetworks = new ArrayList< VirtualNetwork >();
		
		for( VirtualNetwork virtualNetwork: this.getVirtualNetworks() )
			if( virtualNetwork.getEndingDate() == timeInstant )
				completedVirtualNetworks.add( virtualNetwork );
		
		return completedVirtualNetworks;
	}
	private void physicalNetworksReleasing( List< VirtualNetwork > completedVirtualNetworks )
	{
		for( VirtualNetwork completedVirtualNetwork: completedVirtualNetworks )		
			if( completedVirtualNetwork.getState().equals( Network.ACCEPTED_STATE ) )
			{
				completedVirtualNetwork.setChange( true );
				
				if( completedVirtualNetwork.getVirtualSubnetworks() != null )
					for( VirtualNetwork virtualSubnetwork: completedVirtualNetwork.getVirtualSubnetworks() )
						for( PhysicalNetwork physicalNetwork: this.getPhysicalNetworks() )
							if( virtualSubnetwork.getPhysicalNetworkName().equals( physicalNetwork.getName() ) )
								if( !virtualSubnetwork.isDenied() )
								{
									physicalNetwork.setChange( true );
									
									this.physicalNodesReleasing( virtualSubnetwork, physicalNetwork );
									this.physicalLinksReleasing( virtualSubnetwork, physicalNetwork );
								}
			}
	}
	private void physicalNodesReleasing( VirtualNetwork virtualNetwork, PhysicalNetwork physicalNetwork )
	{
		LinkedHashMap< VirtualNode, PhysicalNode > virtualNodesMapping = virtualNetwork.getVirtualNodesMapping();
		Iterator< PhysicalNode > physicalNodesIterator = physicalNetwork.getPhysicalNodes().iterator();
		
		while( physicalNodesIterator.hasNext() )
		{
			PhysicalNode physicalNode = physicalNodesIterator.next();
			
			if( virtualNodesMapping != null )
			{
				Iterator< Entry< VirtualNode, PhysicalNode > > virtualNodesMappingIterator = virtualNodesMapping.entrySet().iterator();

				while( virtualNodesMappingIterator.hasNext() )
				{
					Entry< VirtualNode, PhysicalNode > virtualNodeMapping = virtualNodesMappingIterator.next();
					VirtualNode mappedVirtualNode = virtualNodeMapping.getKey();
					PhysicalNode mappedPhysicalNode = virtualNodeMapping.getValue();
					
					if( physicalNode.getNumber() == mappedPhysicalNode.getNumber() )
					{
						physicalNode.setAvailableCpu( physicalNode.getAvailableCpu() + mappedVirtualNode.getCpu() );
				 		physicalNode.setAvailableMemory( physicalNode.getAvailableMemory() + mappedVirtualNode.getMemory() );
				 		
						if( mappedVirtualNode instanceof VirtualServer  )
							physicalNode.setAvailableDiskSpace( physicalNode.getAvailableDiskSpace() + mappedVirtualNode.getDiskSpace() );
						
						physicalNode.setAvailableVirtualNodesNumber( physicalNode.getAvailableVirtualNodesNumber() + 1 );
						
						physicalNode.remove( mappedVirtualNode );
						mappedVirtualNode.setPhysicalNode( null );
						
						mappedVirtualNode.setCpuUnitTimeUnitPrice( 0 );
						mappedVirtualNode.setMemoryUnitTimeUnitPrice( 0 );
						
						if( mappedVirtualNode.getType().equals( VirtualNode.SERVER_TYPE ) )
							( (VirtualServer) mappedVirtualNode ).setDiskSpaceUnitTimeUnitPrice( 0 );
					}
				}
			}
		}
	}
	private void physicalLinksReleasing( VirtualNetwork virtualNetwork, PhysicalNetwork physicalNetwork )
	{
		int virtualLinksMapping[][][][] = virtualNetwork.getVirtualLinksMapping();
		
		if( virtualLinksMapping != null )
			for( int k = 0; k < virtualNetwork.getVirtualLinks().size(); k++ )
				for( int i = 0; i < physicalNetwork.getNodes().size(); i++ )
					for( int j = 0; j < physicalNetwork.getNodes().size(); j++ )
						for( int a = 0; a < 2; a++ )
							if( virtualLinksMapping[ k ][ i ][ j ][ a ] != 0 )
								for( VirtualLink virtualLink: virtualNetwork.getVirtualLinks() )
									if( virtualLink.getNumber() == k )
										for( PhysicalLink physicalLink: physicalNetwork.getPhysicalLinks() )
											if( i == physicalLink.getFirstNode().getNumber() && j == physicalLink.getSecondNode().getNumber() || i == physicalLink.getSecondNode().getNumber() && j == physicalLink.getFirstNode().getNumber() )
											{
												physicalLink.remove( virtualLink );
												virtualLink.remove( physicalLink );
												virtualLink.setBandwidthUnitTimeUnitPrice( 0 );
											}
	}
		
}
